#include "stdafx.h"
#include "CelestialSystem.h"

namespace Sphericubic
{
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	CelestialObject::CelestialObject(const EType& _eType)
	:	Renderable(),
		m_eType(_eType),
		m_eLevelOfDetail(ELevelOfDetail_UNKNOWN)
	{

	}

	CelestialObject::~CelestialObject()
	{

	}

	const CelestialObject::EType& CelestialObject::GetType() const
	{
		return m_eType;
	}

	void CelestialObject::SetLevelOfDetail(const CelestialObject::ELevelOfDetail& _eELevelOfDetail)
	{
		m_eLevelOfDetail = _eELevelOfDetail;
	}

	const CelestialObject::ELevelOfDetail& CelestialObject::GetLevelOfDetail() const
	{
		return m_eLevelOfDetail;
	}

	void CelestialObject::GetDepthRadiusModifiers(float& _fZNear, float& _fZFar) const
	{
		_fZNear = _fZFar = 1.0f;
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	OrbitingObject::OrbitingObject(const EType& _eType)
	:	CelestialObject(_eType),
		m_vChildren(),
		m_oWorld(),
		m_oAxisToParent(0.0f, 1.0f, 0.0f),
		m_oAxisToSelf(0.0f, 1.0f, 0.0f),
		m_oBoundingSize(0.0f, 0.0f, 0.0f),
		m_pParent(NULL),
		m_fSpeedToParent(0.0f),
		m_fSpeedToSelf(0.0f),
		m_fCurrentRotToParent(0.0f),
		m_fCurrentRotToSelf(0.0f),
		m_fDistanceToParent(0.0f),
		m_fMaxSize(0.0f)
	{
		D3DXMatrixIdentity( &m_oWorld );
	}

	OrbitingObject::~OrbitingObject()
	{

	}

	const Matrix& OrbitingObject::GetWorldTransform() const
	{
		return m_oWorld;
	}

	void OrbitingObject::Update(const float& _fTimeSpeed)
	{
		{
			// rotation axis to self
			m_fCurrentRotToSelf += m_fSpeedToSelf * _fTimeSpeed;
			D3DXQUATERNION oAxisToSelf;
			D3DXQuaternionRotationAxis( &oAxisToSelf, &Vector3(
				m_oAxisToSelf.x,
				m_oAxisToSelf.y,
				m_oAxisToSelf.z ) ,
				m_fCurrentRotToSelf * s_DegToRad );
			D3DXMatrixRotationQuaternion( &m_oWorld, &oAxisToSelf );
		}

		if ( NULL != m_pParent )
		{
			// distance from parent
			Matrix oTemp;
			D3DXMatrixTranslation( &oTemp, m_fDistanceToParent, 0.0f, 0.0f );
			D3DXMatrixMultiply( &m_oWorld, &m_oWorld, &oTemp );

			// rotation axis to parent
			m_fCurrentRotToParent += m_fSpeedToParent * _fTimeSpeed;
			D3DXQUATERNION oAxisToparent;
			D3DXQuaternionRotationAxis( &oAxisToparent, &Vector3(
				m_oAxisToParent.x,
				m_oAxisToParent.y,
				m_oAxisToParent.z) ,
				m_fCurrentRotToParent * s_DegToRad );
			D3DXMatrixRotationQuaternion( &oTemp, &oAxisToparent );
			D3DXMatrixMultiply( &m_oWorld, &m_oWorld, &oTemp );

			// only use parent position info, don't apply the other transformations
			const Matrix& rParentWorld = m_pParent->GetWorldMatrix();
			D3DXMatrixTranslation( &oTemp, rParentWorld._41, rParentWorld._42, rParentWorld._43 );
			D3DXMatrixMultiply( &m_oWorld, &m_oWorld, &oTemp );
		}

		// max size
		if ( 0.0f == m_fMaxSize )
		{
			Vector3 oTRB;
			Vector3 oBLF;
			GetWorldAABB( oTRB, oBLF );
			Vector3 oVecDelta = oTRB - oBLF;
			m_fMaxSize = D3DXVec3Length( &oVecDelta );
		}

		// now update children
		if ( false == m_vChildren.empty() )
		{
			CelestialObjectPtrVec::iterator iPtr = m_vChildren.begin();
			CelestialObjectPtrVec::iterator iEnd = m_vChildren.end();
			while ( iEnd != iPtr )
			{
				(*iPtr)->Update( _fTimeSpeed );
				++iPtr;
			}
		}
	}

	const Matrix& OrbitingObject::GetWorldMatrix() const
	{
		return m_oWorld;
	}

	void OrbitingObject::SetParent(CelestialObject* _pParent)
	{
		m_pParent = _pParent;
	}

	CelestialObject* OrbitingObject::GetParent() const
	{
		return m_pParent;
	}

	void OrbitingObject::AddChild(CelestialObject* _pChild)
	{
		m_vChildren.push_back( _pChild );
	}

	void OrbitingObject::RemoveChild(CelestialObject* _pChild)
	{
		m_vChildren.erase( find( m_vChildren.begin(), m_vChildren.end(), _pChild ) );
	}

	const CelestialObjectPtrVec& OrbitingObject::GetChildren() const
	{
		return m_vChildren;
	}

	void OrbitingObject::GetWorldAABB(Vector3& _oTopRightBack, Vector3& _oBottomLeftFront) const
	{
		_oTopRightBack.x = m_oWorld._41 + m_oBoundingSize.x / 2.0f;
		_oTopRightBack.y = m_oWorld._42 + m_oBoundingSize.y / 2.0f;
		_oTopRightBack.z = m_oWorld._43 + m_oBoundingSize.z / 2.0f;

		_oBottomLeftFront.x = m_oWorld._41 - m_oBoundingSize.x / 2.0f;
		_oBottomLeftFront.y = m_oWorld._42 - m_oBoundingSize.y / 2.0f;
		_oBottomLeftFront.z = m_oWorld._43 - m_oBoundingSize.z / 2.0f;
	}

	const float& OrbitingObject::GetMaxSize() const
	{
		return m_fMaxSize;
	}

	void OrbitingObject::SetRotationAxisToParent(const Vector3& _oAxis)
	{
		m_oAxisToParent = _oAxis;
	}

	void OrbitingObject::SetRotationAxisToSelf(const Vector3& _oAxis)
	{
		m_oAxisToSelf = _oAxis;
	}

	void OrbitingObject::SetRotationSpeedToParent(const float& _fSpeed)
	{
		m_fSpeedToParent = _fSpeed;
	}

	void OrbitingObject::SetRotationSpeedToSelf(const float& _fSpeed)
	{
		m_fSpeedToSelf = _fSpeed;
	}

	void OrbitingObject::SetDistanceToParent(const float& _fDistance)
	{
		m_fDistanceToParent = _fDistance;
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	ViewManager::ViewManager()
	:	m_pRenderer(NULL),
		m_pCameraPos(NULL),
		m_fPixelRatio(1.0f)
	{

	}

	ViewManager::~ViewManager()
	{

	}

	void ViewManager::SetRenderer(D3DRenderer* _pRenderer)
	{
		m_pRenderer = _pRenderer;
	}

	void ViewManager::SetCameraPos(Vector3* _pCameraPos)
	{
		m_pCameraPos = _pCameraPos;
	}

	void ViewManager::SetPixelRatio(const float& _fPixelRatio)
	{
		m_fPixelRatio = _fPixelRatio;
	}

	void ViewManager::Update(CelestialObject& _rCelestialObject)
	{
		if ( NULL != m_pRenderer )
		{
			m_pRenderer->GetCameraDir( m_oCamFrontDir, m_oCamRightDir, m_oCamUpDir, true );
			Traverse( _rCelestialObject );
		}
	}

	float ViewManager::GetObjectPixelSize(const CelestialObject& _rCelestialObject, float* _pfZNear, float* _pfZFar) const
	{
		float fResult = -1.0f;

		if ( NULL != m_pCameraPos )
		{
			const float fHalfSize = _rCelestialObject.GetMaxSize() / 2.0f;
			const Matrix& oWorld = _rCelestialObject.GetWorldMatrix();
			const Vector3 oObjectPos3( oWorld._41, oWorld._42, oWorld._43 );
			const Vector3 oVecDelta = oObjectPos3 - *m_pCameraPos;
			const float fDistance0 = D3DXVec3Length( &oVecDelta );
			const float fBoundingRadius = fHalfSize * 1.1f;

			Vector3 oCamToCelObjDir;
			D3DXVec3Normalize( &oCamToCelObjDir, &oVecDelta );
			const float fDistance = D3DXVec3Dot( &m_oCamFrontDir, &oCamToCelObjDir ) * fDistance0;

			fResult = fHalfSize / ( fDistance * m_fPixelRatio );

			float fZNearModifier = 1.0f;
			float fZFarModifier = 1.0f;
			_rCelestialObject.GetDepthRadiusModifiers( fZNearModifier, fZFarModifier );
			if ( NULL != _pfZNear )
			{
				*_pfZNear = fDistance - fBoundingRadius * fZNearModifier;
			}
			if ( NULL != _pfZFar )
			{
				*_pfZFar = fDistance + fBoundingRadius * fZFarModifier;
			}
		}

		return fResult;
	}

	void ViewManager::DetermineObjectLevelOfDetail(CelestialObject& _rCelestialObject, float* _pfZNear, float* _pfZFar) const
	{
		const float fSize = GetObjectPixelSize( _rCelestialObject, _pfZNear, _pfZFar );
		if ( 1.0f >= fSize )
		{
			_rCelestialObject.SetLevelOfDetail( CelestialObject::ELevelOfDetail_POINT );
		}
		else if ( 10.0f >= fSize )
		{
			_rCelestialObject.SetLevelOfDetail( CelestialObject::ELevelOfDetail_BILLBOARD );
		}
		else if ( 50.0f >= fSize )
		{
			_rCelestialObject.SetLevelOfDetail( CelestialObject::ELevelOfDetail_UNTEXTUREDSIMPLEMESH );
		}
		else if ( 100.0f >= fSize )
		{
			_rCelestialObject.SetLevelOfDetail( CelestialObject::ELevelOfDetail_TEXTUREDSIMPLEMESH );
		}
		else
		{
			_rCelestialObject.SetLevelOfDetail( CelestialObject::ELevelOfDetail_DETAILMESH );
		}
	}

	void ViewManager::Traverse(CelestialObject& _rCelestialObject)
	{
		float fZNear;
		float fZFar;

		DetermineObjectLevelOfDetail( _rCelestialObject, &fZNear, &fZFar );
		m_pRenderer->RenderRequest( &_rCelestialObject, "default", fZNear, fZFar );

		const CelestialObjectPtrVec vChildren = _rCelestialObject.GetChildren();
		CelestialObjectPtrVec::const_iterator iCelObj = vChildren.begin();
		CelestialObjectPtrVec::const_iterator iEnd = vChildren.end();
		while ( iEnd != iCelObj )
		{
			CelestialObject* pCelObj = *iCelObj;
			Traverse( *pCelObj );
			++iCelObj;
		}
	}

}
